#include "mera.h"
#include <QFileInfo>
#include <QDebug>
#include <chrono>
#include <vector>

Parametr::Parametr(MERA *parent)
    :QIODevice(parent)
{
    m_parent=parent;

    m_xData=nullptr;
    m_yData=nullptr;
    m_tx.column_size = 0;
    m_tx.tx = new Approx();
    m_tx.m_parent=this;

    this->setErrorString("");
}

HeadeParametr_t Parametr::header() const
{
    return m_header;
}

QString Parametr::name() const
{
    return m_header.Name;
}

void Parametr::setName(const QString &name)
{
    this->m_header.Name=name;
}

QString Parametr::path() const
{
    return m_parent->path() + "/" + name();
}

Parametr &Parametr::operator=(const QString &name)
{
    QSettings *header = m_parent->meraHeader();
    header->beginGroup(name);
    // чтение значений
    m_header.Name = header->value("Name").toString();
    qDebug() << m_header.Name;
    header->endGroup();

    return *this;
}

QByteArray *Parametr::yData() const
{
    return m_yData;
}

size_t Parametr::unread_size()
{
    return m_unread_size;
}

void Parametr::setYData(QByteArray *newYData)
{
    m_yData = newYData;
}

void Parametr::clearYData()
{
    m_yData->clear();
}

QByteArray *Parametr::xData() const
{
    return m_xData;
}

void Parametr::setXData(QByteArray *newXData)
{
    m_xData = newXData;
}

quint64 Parametr::writeX()
{
    QFile x(this->path() + ".x");
    if(!x.open(QIODevice::WriteOnly)){
        this->setErrorString(this->path() + " " +x.errorString());
        qCritical() << this->errorString();
        return -1;
    }
    if(!m_xData){
        this->setErrorString(this->path() + tr(" x array is empty "));
        qCritical() << this->errorString();
        return -1;
    }
    quint64 ret=x.write(*m_xData);
    x.close();
    return ret;
}

quint64 Parametr::writeDat()
{
    QFile dat(this->path() + ".x");
    if(!dat.open(QIODevice::WriteOnly)){
        this->setErrorString(this->path() + " " +dat.errorString());
        qCritical() << this->errorString();
        return -1;
    }
    if(!m_yData){
        this->setErrorString(this->path() + tr(" dat array is empty "));
        qCritical() << this->errorString();
        return -1;
    }
    quint64 ret=dat.write(*m_yData);
    dat.close();
    return ret;
}

quint64 Parametr::readX()
{

    QFileInfo x_file(this->path() + ".x");
    if(x_file.exists()){
        QFile x(this->path() + ".dat");
        if(!x.open(QIODevice::ReadOnly)){
            this->setErrorString(this->path() + " " +x.errorString());
            qCritical() << this->errorString();
            return -1;
        }
        if(!m_xData){
            m_xData = new QByteArray;
        }
        m_xData->clear();
        m_xData->resize(x_file.size());

        *m_xData=x.readAll();
        x.close();

    }else{
        qDebug() << "file not exist " << x_file.absoluteFilePath();
        return -1;
    }

    return m_xData->size();
}

void Parametr::openDat()
{
    QFileInfo dat_file(this->path() + ".dat");
    if(dat_file.exists()){
       m_file = new QFile(this->path() + ".dat");
        if(!m_file->open(QIODevice::ReadOnly)){
            this->setErrorString(this->path() + " " +m_file->errorString());
            qCritical() << this->errorString();
        }
        if(!m_yData){
            m_yData = new QByteArray;
        }
        m_yData->clear();
        m_unread_size = dat_file.size();
    }else{
        qDebug() << "file not exist " << dat_file.absoluteFilePath();
    }
}

QByteArray Parametr::readSomeDat(qint64 size)
{
    m_unread_size -= size;
    QByteArray res = m_file->read(size);
    if(m_unread_size <= 0)
    {
        m_file->close();
        qDebug() << "file closed";
    }
    return res;
}

quint64 Parametr::readDat()
{
    QFileInfo dat_file(this->path() + ".dat");
    if(dat_file.exists()){
        QFile dat(this->path() + ".dat");
        if(!dat.open(QIODevice::ReadOnly)){
            this->setErrorString(this->path() + " " +dat.errorString());
            qCritical() << this->errorString();
            return -1;
        }
        if(!m_yData){
            m_yData = new QByteArray;
        }
        m_yData->clear();
        m_yData->resize(dat_file.size());

        for(int i = 0; i < 0; i++)
        {
            QByteArray *tst = new QByteArray;
            *tst = dat.read(sizeof(float));
            float *ar = (float*)tst->data();
            qDebug() << ar[0];
        }
        *m_yData = dat.readAll();
        qDebug() << "stop read";
        dat.close();
        qDebug() << m_yData->size();
    }else{
        qDebug() << "file not exist " << dat_file.absoluteFilePath();
        return -1;
    }
    return m_yData->size();
}

quint64 Parametr::readTx()
{
    qDebug() << this->path() + ".tx0";
    QFileInfo tx_file(this->path() + ".tx0");
    if(tx_file.exists()){
        QFile tx(this->path() + ".tx0");
        if(!tx.open(QIODevice::ReadOnly)){
            this->setErrorString(this->path() + " " +tx.errorString());
            qCritical() << this->errorString();
            return -1;
        }
        QString all = tx.readAll();
        QStringList lines = all.split("\n", Qt::SkipEmptyParts);
        QRegExp whitespace_or_tab("[ 	]");
        if(not lines.size())
        {
            m_tx.column_size = 0;
            return 0;
        }
        else if(lines[0].split(whitespace_or_tab).size() == 1)
        {
            m_tx.column_size = 0;
            double *poly_koefs = new double[lines.size()];
            for(int i = 0; i < lines.size(); i++)
            {
                poly_koefs[i] = lines[i].toDouble();
            }
            m_tx.tx->set_poly(poly_koefs, lines.size());
        }
        else if(lines[0].split(whitespace_or_tab).size() == 2)
        {
            m_tx.column_size = lines.size();
            m_tx.column2 = new double[m_tx.column_size];
            m_tx.column1 = new double[m_tx.column_size];
            for(int i = 0; i < m_tx.column_size; i++)
            {
                QStringList pair = lines[i].split(whitespace_or_tab);
                m_tx.column1[i] = pair[0].toDouble();
                m_tx.column2[i] = pair[1].toDouble();
            }
        }
        else
            return -1;
    }
    return 1;
}

qint64 Parametr::readData(char *data, qint64 maxlen)
{
    Q_UNUSED(data)
    Q_UNUSED(maxlen)
    Q_ASSERT("use readX and readY");
    return -1;
}

qint64 Parametr::writeData(const char *data, qint64 len)
{
    Q_UNUSED(data)
    Q_UNUSED(len)
    Q_ASSERT("use writeX and writeY");
    return -1;
}

QTextCodec *m_codecForRead=nullptr;
QTextCodec *m_codecForWrite=nullptr;

MERA::MERA(QObject *parent, QString file)
    :QObject(parent),
      m_meraHeader(file,QSettings::IniFormat)
{
    qDebug() << file;
    m_codecForRead = nullptr;
    m_codecForWrite=nullptr;
    const QSettings::Format meraFormat = QSettings::registerFormat("mera", readMera, writeMera, Qt::CaseSensitive);

    if(meraFormat == QSettings::InvalidFormat){
        //        std::cerr << "Ошибка создания mera-формата";

    }
    m_meraHeader.setDefaultFormat(meraFormat);
    QFileInfo info(file);
    m_path=info.absolutePath();
}

bool MERA::readMera(QIODevice &device, QSettings::SettingsMap &map)
{
    if(device.isOpen() == false){
        return false;
    }
    QTextStream inTs(&device);
    inTs.setCodec(m_codecForRead);
    QString group;                  // текущая группа
    group.clear();
    QString line;
    QString key;
    key.clear();
    QVariant value;
    while(!inTs.atEnd()){
        line.clear();
        line = inTs.readLine();
        qDebug() << line;
        if((line.at(0) == '[') && (line.at(line.size()-1) == ']')){
            group = line.mid(1,line.size()-2);
        } else { // ключ = значение
            int index = line.indexOf("=");
            if(index > -1){
                key = group + "/" + line.mid(0,index);
                value = QVariant(line.mid(index+1));
                map.insert(key, value);
            }
        }
    }
    return true;
}

bool MERA::writeMera(QIODevice &device, const QSettings::SettingsMap &map)
{
    if(!device.isOpen())    return false;
    QString lastGroup = "";

    QTextStream outTs(&device);
    outTs.setCodec(m_codecForWrite);

    foreach(const QString &group_key, map.keys()){
        int index = group_key.indexOf("/");
        if(index == -1){
            continue;
        }
        // запись группы
        QString group = group_key.mid(0, index);
        if(group != lastGroup){
            outTs << QString("[%1]").arg(group) << "\r\n"; //endl;
            lastGroup = group;
        }
        // запись ключ=значение
        QString key = group_key.mid(index+1);
        QString value = map.value(group_key).toString();
        outTs << QString("%1=%2")
                 .arg(key)
                 .arg(value)
              << "\r\n"; //endl;


    }
    return true;
}

const QList<Parametr *> &MERA::paramList() const
{
    return m_paramList;
}

QString MERA::path() const
{
    return m_path;
}

void MERA::setCodecForRead(QTextCodec *newCodecForRead)
{
    m_codecForRead = newCodecForRead;
}

bool MERA::load()
{
    QStringList groups = m_meraHeader.childGroups();
    foreach (QString group, groups) {
        if(group == "MERA")
            continue;
        Parametr *param=new Parametr(this);
        param->setName(group);
        this->m_paramList.append(param);
    }
    return this->m_paramList.size()>0;
}

void MERA::setCodecForWrite(QTextCodec *newCodecForWrite)
{
    m_codecForWrite = newCodecForWrite;
}

QSettings* MERA::meraHeader()
{
    return &m_meraHeader;
}

void MERA::setCodec(QTextCodec *newCodec)
{
    m_codecForRead = newCodec;
    m_codecForWrite = newCodec;
}



Parametr &Parametr::operator<<(const QPointF &point)
{
    this->append(point.x(),point.y());
    return *this;
}

bool Parametr::open(OpenMode mode)
{
    if(mode == QIODevice::ReadOnly){
        this->readTx();
        if(this->readDat()<0)
            return false;
        this->readX();
        return true;
    }

    return false;
}

bool Parametr::open_only(OpenMode mode)
{
    if(mode == QIODevice::ReadOnly){
        this->readTx();
        this->openDat();
        this->readX();
        return true;
    }

    return false;
}

TxData_t *Parametr::tx()
{
    return &m_tx;
}

MERA *Parametr::mera()
{
    return  m_parent;
}

void TxData::mnk()
{
    this->tx->least_squares_auto_pow(this->column1, this->column2, this->column_size);
}

void TxData::lineal_pices()
{
    this->tx->lineal_pices(this->column1, this->column2, this->column_size);
}

double *TxData::calc_poly()
{
    qDebug() << "poly func start";
    double *x_values=(double*)this->m_parent->yData()->data();
    ssize_t x_size = this->m_parent->yData()->size()/sizeof (double);
    double *res= new double[x_size];
    Parametr *calc = new Parametr(m_parent->mera());
    calc->setName("Temp");

    auto t1 = std::chrono::steady_clock::now();
    qDebug() << "calc start";
    for(int i = 0; i < x_size; i++)
    {
        res[i] = this->tx->calc_polynom_point(x_values[i]);
    }
    auto t2 = std::chrono::steady_clock::now() - t1;
    qDebug() << "poly_size " << this->tx->get_poly_size();
    qDebug() << "calc_polynom time " << std::chrono::duration_cast<std::chrono::milliseconds>(t2).count() << " ms";
    return res;
}

void TxData::calc_poly_part(vector<double> *output, int count)
{
    QByteArray x_data = this->m_parent->readSomeDat(count * sizeof(double));
    double *x_values=(double*)x_data.data();
    for(int i = 0; i < count; i++)
    {
        (*output)[i] = this->tx->calc_polynom_point(x_values[i]);
        //(*output)[i] = x_values[i];
    }
    x_data.clear();
}

double *TxData::calc_lineal()
{
    double *x_values=(double*)this->m_parent->yData()->data();
    ssize_t x_size = this->m_parent->yData()->size()/sizeof (double);
    double *res= new double[x_size];
    Parametr *calc = new Parametr(m_parent->mera());
    calc->setName("Temp");   //временно
    qDebug() << "start";
    auto t1 = std::chrono::steady_clock::now();
    for(int i = 0; i < x_size; i++)
    {
        res[i] = this->tx->calc_split_lineal_point(x_values[i]);
    }
    auto t2 = std::chrono::steady_clock::now() - t1;
    qDebug() << "calc_split_lineal_point time " << std::chrono::duration_cast<std::chrono::milliseconds>(t2).count() << " ms";
    qDebug() << "end";
    return res;
}


